#!/usr/bin/python

# chkconfig:   2345 90 10
# description:  appdog is a apps daemon tool
# version: v0.2
# author: joe
# required: above python2.6

### BEGIN INIT INFO
# Provides:          giiwa
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the appdog
# Description:       starts appdog using start-stop-daemon
### END INIT INFO

import re, os, sys, time, socket, threading, subprocess, ConfigParser, smtplib, httplib,urllib
from email.mime.text import MIMEText

global running,logfile,conf,sock,shutdown
running=True
shutdown=False
logfile="/var/log/appdog.log"
re_meminfo_parser = re.compile(r'^(?P<key>\S*):\s*(?P<value>\d*)\s*kB')  

def _log(x):
	global logfile
	f1=open(logfile, "ab+")
	print >>f1, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),x
	f1.close()

def _email_notify(x, sub, content):
	global conf
	me=conf.get(x, 'smtp_from')
	to=conf.get(x, 'smtp_to')
	mail_user=conf.get(x, 'smtp_user')
	mail_pass=conf.get(x, 'smtp_pass')
	mail_host=conf.get(x, 'smtp_server')
	msg = MIMEText('host:' + conf.get(x, 'host') + '/' + socket.gethostname() + '\n' + content + '\n' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
	msg['Subject'] = sub
	msg['From'] = me
	msg['To'] = to
	try:
		s = smtplib.SMTP()
		s.connect(mail_host)
		s.login(mail_user,mail_pass)
		s.sendmail(me, to, msg.as_string())
		s.close()
		_log('email: ' + to + ', msg:' + msg.as_string())
		return 0
	except Exception, e:
		_log(str(e))
		return 0
		
def _alert_notify(x, sub, content):
	global conf
	host = conf.get(x, 'host')
	url=conf.get(x, 'url')
	conn=httplib.HTTPConnection(host)
	data = urllib.urlencode({'@tag': sub, '@content': content}) 
	conn.request('GET', url, data) 
	return 0
		
def _notify(subject, content):
	global conf
	if conf.has_section('notify:alert'):
		_alert_notify('notify:alert', subject, content)
	if conf.has_section('notify:email'):
		_email_notify('notify:email', subject, content)
	if conf.has_section('notify:sms'):
		_sms_notify('notify:sms', subject, content)

def _load_config():
	global conf, logfile
        conf=ConfigParser.ConfigParser()
	try:
		if os.path.isfile("/etc/appdog/apps.conf"):
			conf.read("/etc/appdog/apps.conf")
		else:
			conf.read("apps.conf")
		logfile=conf.get("global", "log")
	except Exception, e:
		print 'not found apps.conf in local or /etc/appdog/'
		_log('not found apps.conf in local or /etc/appdog/')
		sys.exit(0)

def _callback(arg):
	print "i'm out", arg
	
def _startx(x, u, cwd):
	global sock, logfile
	_log("starting:" + x)
	try:
		if len(u)>0:
			c="sudo -E -u " + u + " " + x + ">" + logfile + " 2>&1"
			_log(c)
			subprocess.Popen(c, shell=True, cwd=cwd)
		else:
			c=x + ">" + logfile + " 2>&1"
			_log(c)
			subprocess.Popen(x, shell=True, cwd=cwd)
	except Exception, e:
		_log("failed: " + x)
		print e
		print "failed:" + x
		
def _pidx(x):
	try:
		pid = int(os.popen("ps -ef | grep '"+x+"' | grep -v 'grep' | awk '{print $2}'").readlines()[0])
		return pid
	except:
		return -1
def _stopx(x):
	x=x.strip()
	pid=_pidx(x)
	if pid>0:
		subprocess.call("kill -9 " + str(pid), shell=True)
		s="[" + x +"] was stoped !"		
		print s
		_log(s)

def _mem_usage():
	result={}   
	try:  
		fd=open('/proc/meminfo', 'r')  
		lines=fd.readlines()  
	finally:  
		if fd:  
			fd.close()  
	for line in lines:  
		match=re_meminfo_parser.match(line)  
		if not match:  
			continue # skip lines that don't parse  
		key, value=match.groups(['key', 'value'])  
		result[key]=float(value)  
	return (result["MemTotal"]-result["MemFree"])/result["MemTotal"] 

def _read_cpu_usage():  
	try:  
		fd = open("/proc/stat", 'r')  
		lines = fd.readlines()  
	finally:  
		if fd:  
			fd.close()  
	for line in lines:  
		l = line.split()  
		if len(l) < 5:  
			continue  
		if l[0].startswith('cpu'):  
			return l  
	return []

def _cpu_usage():  
	cpustr=_read_cpu_usage()  
	if not cpustr:  
		return 0  
	usni1=long(cpustr[1])+long(cpustr[2])+long(cpustr[3])+long(cpustr[5])+long(cpustr[6])+long(cpustr[7])+long(cpustr[4])  
	usn1=long(cpustr[1])+long(cpustr[2])+long(cpustr[3])  
	time.sleep(1) 
	cpustr=_read_cpu_usage()  
	if not cpustr:  
		return 0  
	usni2=long(cpustr[1])+long(cpustr[2])+float(cpustr[3])+long(cpustr[5])+long(cpustr[6])+long(cpustr[7])+long(cpustr[4])  
	usn2=long(cpustr[1])+long(cpustr[2])+long(cpustr[3])  
	cpupe=(usn2-usn1)/(usni2-usni1)  
	return cpupe
def _disk_usage(path):
        st = os.statvfs(path)
        total = st.f_blocks * st.f_frsize
        used = (st.f_blocks - st.f_bfree) * st.f_frsize
        return used/total

def _mem_main(x):
	global running,conf
	threshold=conf.getfloat(x, 'threshold')
	try:
		check=conf.getfloat(x, 'check')
	except:
		check=60
	status=-1
	while running:
		mem=_mem_usage()
		if mem>threshold:
			if status<0:
				'''warning'''
				_notify('warning:mem', 'mem is high: ' + '%0.0f'%(mem * 100) + '%, threshold:' + '%0.0f'%(threshold*100) + '%')
				status=1
		else:
			if status>0:
				'''reback'''
				_notify('recover:mem', 'mem back: ' + '%0.0f'%(mem * 100) + '%, threshold:' + '%0.0f'%(threshold*100) + '%')
				status=-1
		time.sleep(check)
	else:
		return 0
def _disk_main(x):
	global running,conf
	threshold=conf.getfloat(x, 'threshold')
	path=conf.get(x, 'path')
	try:
		check=conf.getfloat(x, 'check')
	except:
		check=600
	status=-1
	while running:
		disk=_disk_usage(path)
		if disk>threshold:
			if status<0:
				'''warning'''
				_notify('warning:disk', 'disk is high: ' + '%0.0f'%(disk * 100) + '%, threshold:' + '%0.0f'%(threshold*100) + '%')
				status=1
		else:
			if status>0:
				'''reback'''
				_notify('recover:disk', 'disk back: ' + '%0.0f'%(disk * 100) + '%, threshold:' + '%0.0f'%(threshold*100) + '%')
				status=-1
		time.sleep(check)
	else:
		return 0

def _cpu_main(x):
	global running,conf
	threshold=conf.getfloat(x, 'threshold')
	try:
		check=conf.getfloat(x, 'check')
	except:
		check=60
	status=-1
	while running:
		cpu=_cpu_usage()
		if cpu>threshold:
			if status<0:
				'''warning'''
				_notify('warning:cpu', 'cpu is high: ' + '%0.0f'%(cpu * 100) + '%, threshold:' + '%0.0f'%(threshold*100) + '%')
				status=1
		else:
			if status>0:
				'''reback'''
				_notify('recover:cpu', 'cpu back: ' + '%0.0f'%(cpu * 100) + '%, threshold:' + '%0.0f'%(threshold*100) + '%')
				status=-1
		time.sleep(check)
	else:
		return 0

def _remote_main():
	global running, sock,shutdown
	try:
		while running:  
			connection,address = sock.accept()  
			try:  
				connection.settimeout(5) 
				service=True
				while service:
					cmd = connection.recv(32)
					if cmd == 'shutdown':  
						running=False
						service=False
						shutdown=True
						connection.send('ok!')
					elif cmd == 'stop':
						shutdown=False
						service=False
						running=False
						connection.send('ok!') 
					elif cmd == 'start':
						_start()
						connection.send('ok!')
					elif cmd == 'quit':
						service=False
						connection.send('ok!')
	  			else:
					connection.close()
			except socket.timeout:  
				print 'time out'  
		else:
			_log('appdog is down')
			_notify('warning:appdog', 'appdog is down')	
	except Exception, e:
		_log(str(e))
	finally:
		sock.shutdown(socket.SHUT_RD)
		sock.close()

def _app_main(x):
	global conf, running
	command=conf.get(x, "start")
	pattern=conf.get(x, "pattern")
	try:
		user=conf.get(x, "user")
	except:
		user=""
	try:
		check=conf.getfloat(x, "check")
	except:
		check=0.2
	try:
		enabled=conf.getint(x, "enabled")
	except:
		enabled=1
	try:
		path=conf.get(x, "path")
	except:
		path=""
	
	try:
		notify=conf.getint(x, "notify")
	except:
		notify=0		
	if enabled>0:
		while running:	
			if _pidx(pattern)>0:
				time.sleep(check)
			else:
				if notify>0:
					_notify("warning:" + x, x + " was down, starting it")

				_startx(command, user, path)

				if check>5:
					time.sleep(check)
				else:
					time.sleep(10)
		else:
			if shutdown:
				_stopx(pattern)

def _mon_start(x):
	if x=="mon:cpu":
		t = threading.Thread(target=_cpu_main, args=(x,))	
		###t.setDaemon(True)
		t.start()
	elif x=="mon:mem":
		t = threading.Thread(target=_mem_main, args=(x,))	
		###t.setDaemon(True)
		t.start()
	elif x=="mon:disk":
		t = threading.Thread(target=_disk_main, args=(x,))	
		###t.setDaemon(True)
		t.start()
		
def _app_start(x):
	try:
		t = threading.Thread(target=_app_main, args=(x,))	
		###t.setDaemon(True)
		t.start()
	except Exception,e:
		_log(str(e))
	
def _start():
	global conf, running, sock
	_load_config()

	pid=os.fork()
	if pid>0:
		pidfile=conf.get("global", "pid")
		try:
			f=open(pidfile, "wb+")
			print >>f, pid
			f.close()
			sys.exit(0)
		except Exception, e:
			print e
			_log(str(e))
			sys.exit(0)

	port=conf.getint("global", "port")
	sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
	try:
		sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		sock.bind(('localhost', port))  
		sock.listen(1)
		t = threading.Thread(target=_remote_main, args=())	
		###t.setDaemon(True)
		t.start()
	except Exception,e:
		_log(str(e))
		running=False
		print "already running! aborted"
		sys.exit(0)

	for s in conf.sections():
		if "app:" in s:
			_app_start(s)
		elif "mon:" in s:
			_mon_start(s)

	_log("appdog started")
	print "appdog started"
	while running:
		time.sleep(1)
	else:
		sys.exit(0)

def _stop():
	global conf
	_load_config()
	try:	
		sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
		port=conf.getint("global", "port")	
		sock.connect(('localhost', port))  
		sock.send('stop') 
		print "stoping:" + sock.recv(1024)  
		sock.close()  	
	except:
		print "not running"

def _shutdown():
	global conf
	_load_config()
	try:	
		sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
		port=conf.getint("global", "port")	
		sock.connect(('localhost', port))  
		sock.send('shutdown') 
		print "stoping all:" + sock.recv(1024)  
		sock.close() 
	except:
		print "not running"
	
def _usage():
	print "Help: \nappdog [start|stop|shutdown]"
	print "\tstop: stop the appdog, but the apps still running"
	print "\tshutdown: stop the appdog and all the apps"

def _console():
	_usage()

if __name__=="__main__":
	if len(sys.argv) > 1:
		a=sys.argv[1]
	else:
		a=""
	if a=="start":
		_start()
	elif a=="stop":
		_stop()
	elif a=="shutdown":
		_shutdown()
	else:
		_console()
